/*
 * Graphic.cpp
 *
 *  Created on: 2012-9-15
 *      Author: yangentao@gmail.com
 */

#include "UiUtil.h"
#include "Graphic.h"

using namespace std;

#pragma comment(lib, "Msimg32.lib")




void Canvas::release() {
	if (penOld) {
		SelectPen(hdc, penOld);
		penOld = 0;
	}
//	if (pen) {
//		DeletePen(pen);
//		pen = 0;
//	}
	if (brushOld) {
		SelectBrush(hdc, brushOld);
		brushOld = 0;
	}
//	if (brush) {
//		::DeleteBrush(brush);
//		brush = 0;
//	}
	if (fontOld) {
		// Fonts are not deleted as they are owned by a Font object
		SelectFont(hdc, fontOld);
		fontOld = 0;
	}
	font = 0;
	if (bitmapOld) {
		SelectBitmap(hdc, bitmapOld);
		bitmapOld = 0;
	}
	if (bitmap) {
		::DeleteBitmap(bitmap);
		bitmap = 0;
	}

	if (hdcOwned) {
		::DeleteDC(hdc);
		hdc = 0;
		hdcOwned = false;
	}
}






void Canvas::transparentBlt(Point p, Bitmap bmp, COLORREF transparentColor) {
	if (!bmp.enable()) {
		Log << "bitmap is null" << endl;
	}
	Size sz = bmp.getSize();
	HDC memDC = CreateCompatibleDC(hdc);
	SelectBitmap(memDC, bmp);
	TransparentBlt(hdc, p.x, p.y, sz.cx, sz.cy, memDC, 0, 0, sz.cx, sz.cy, transparentColor);
	DeleteDC(memDC);
}
void Canvas::bitblt(Point p, Bitmap bmp) {
	if (!bmp.enable()) {
		Log << "bitmap is null" << endl;
	}
	Size sz = bmp.getSize();
	HDC memDC = CreateCompatibleDC(hdc);
	SelectBitmap(memDC, bmp);
	BitBlt(hdc, p.x, p.y, sz.cx, sz.cy, memDC, 0, 0, SRCCOPY);
	DeleteDC(memDC);
}

// Text drawing may fail if the text is too big.
// If it does fail, slice up into segments and draw each segment.
const static int maxSegmentLength = 0x200;
void Canvas::drawText(Rect rc, int ybase, String s, UINT fuOptions) {
	const WideText tbuf(s.buffer(), s.size());
	if (!::ExtTextOutW(hdc, rc.left, ybase, fuOptions, &rc, tbuf.buf, tbuf.len, NULL)) {
		SIZE sz = { 0, 0 };
		int pos = 0;
		int x = rc.left;
		while (tbuf.len > pos) {
			int seglen = Min(maxSegmentLength, tbuf.len - pos);
			if (!::ExtTextOutW(hdc, x, ybase, fuOptions, &rc, tbuf.buf + pos, seglen, NULL)) {
				assert(false);
				return;
			}
			::GetTextExtentPoint32W(hdc, tbuf.buf + pos, seglen, &sz);
			x += sz.cx;
			pos += seglen;
		}
	}
}

void Canvas::measureWidths(const char *s, int len, int *positions) {
	SIZE sz = { 0, 0 };
	int fit = 0;
	const WideText tbuf(s, len);
	Vector<int> poses(tbuf.len);
	poses.setLength(tbuf.len);
	fit = tbuf.len;
	if (!::GetTextExtentExPointW(hdc, tbuf.buf, tbuf.len, tbuf.len, &fit, poses.getBufferWrite(), &sz)) {
		// Likely to have failed because on Windows 9x where function not available
		// So measure the character widths by measuring each initial substring
		// Turns a linear operation into a qudratic but seems fast enough on test files
		for (int widthSS = 0; widthSS < tbuf.len; widthSS++) {
			::GetTextExtentPoint32W(hdc, tbuf.buf, widthSS + 1, &sz);
			poses[widthSS] = sz.cx;
		}
	}
	// Map the widths given for UTF-16 characters back onto the UTF-8 input string
	int ui = 0;
	const unsigned char *us = reinterpret_cast<const unsigned char *>(s);
	int i = 0;
	while (ui < fit) {
		unsigned char uch = us[i];
		unsigned int lenChar = 1;
		if (uch >= (0x80 + 0x40 + 0x20 + 0x10)) {
			lenChar = 4;
			ui++;
		} else if (uch >= (0x80 + 0x40 + 0x20)) {
			lenChar = 3;
		} else if (uch >= (0x80)) {
			lenChar = 2;
		}
		for (unsigned int bytePos = 0; (bytePos < lenChar) && (i < len); bytePos++) {
			positions[i++] = poses[ui];
		}
		ui++;
	}
	int lastPos = 0;
	if (i > 0)
		lastPos = positions[i - 1];
	while (i < len) {
		positions[i++] = lastPos;
	}
}

void Canvas::arc90(Point center, int radius, int quadrant) {
	assert(quadrant >=0);
	//::Arc(g.getDC(), 0, 0, TAB_RADIUS*2 , TAB_RADIUS*2 , TAB_RADIUS, 0, 0, TAB_RADIUS);
	int xFrom = 0;
	int yFrom = 0;
	int xTo = 0;
	int yTo = 0;
	quadrant = quadrant % 4;
	switch (quadrant) {
	case 0:
		xFrom = center.x + radius;
		yFrom = center.y;
		xTo = center.x;
		yTo = center.y - radius;
		break;
	case 1:
		xFrom = center.x;
		yFrom = center.y - radius;
		xTo = center.x - radius;
		yTo = center.y;
		break;
	case 2:
		xFrom = center.x - radius;
		yFrom = center.y;
		xTo = center.x;
		yTo = center.y + radius;
		break;
	case 3:
		xFrom = center.x;
		yFrom = center.y + radius;
		xTo = center.x + radius;
		yTo = center.y;
		break;
	}

	::Arc(hdc, center.x - radius, center.y - radius, center.x + radius, center.y + radius, xFrom, yFrom, xTo, yTo);
}

void Canvas::bezierHorz(Point from, Point to, int seg) {
	int segSize = (to.x - from.x) / seg;
	POINT pts[4] = { { from.x, from.y }, { from.x + segSize, from.y }, { to.x - segSize, to.y }, { to.x, to.y } };
	::PolyBezier(hdc, pts, 4);
}

map<COLORREF, HPEN> penCache;
map<COLORREF, HBRUSH> brushCache;

void Canvas::clearCache() {
	{
		map<COLORREF, HPEN>::iterator it = penCache.begin();
		while (it != penCache.end()) {
			DeleteObject(it->second);
			++it;
		}
		penCache.clear();
	}
	{
		map<COLORREF, HBRUSH>::iterator it2 = brushCache.begin();
		while (it2 != brushCache.end()) {
			DeleteObject(it2->second);
			++it2;
		}
		brushCache.clear();
	}
}

HPEN Canvas::createPen(COLORREF color) {
	map<COLORREF, HPEN>::iterator it = penCache.find(color);
	if (it != penCache.end()) {
		return it->second;
	}
	HPEN pen = CreatePen(PS_SOLID, 1, color);
	penCache[color] = pen;
	return pen;
}

HBRUSH Canvas::createBrush(COLORREF color) {
	map<COLORREF, HBRUSH>::iterator it = brushCache.find(color);
	if (it != brushCache.end()) {
		return it->second;
	}
	COLORREF colourNearest = ::GetNearestColor(GetDC(0), color);
	HBRUSH br = CreateSolidBrush(colourNearest);
	brushCache[color] = br;
	return br;
}


